package de.tabiteuea.gui.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import de.tabiteuea.gui.models.Language;
import de.tabiteuea.tools.BaseFormHandler;

/**
 * Main IO class.
 * <p>
 * Responsible for parsing the input String and doing all calculations. These
 * calculations are: Number of words and sentences. Average sentence length and
 * word coverage.
 * 
 * @author Sebastian Lippert
 */

public class TextProcessor {

	private HashMap<String, Integer> countMap;
	private int wordCount, sentenceCount;
	private List<String> wordList;
	private BaseFormHandler baseFormHandler;
	private boolean useBaseform;

	/**
	 * Constructor
	 * 
	 * @param language
	 *            Language of the input
	 * 
	 * @param useBaseform
	 *            Use base form reduction or not
	 */

	public TextProcessor(Language language, boolean useBaseform) {

		// the BaseFormHandler is responsible for the reduction of a word to its
		// base form
		this.useBaseform = useBaseform;
		if (useBaseform)
			baseFormHandler = new BaseFormHandler(language);
		// map that holds all tuples (word, count)
		countMap = new HashMap<String, Integer>();
		wordCount = 0;
		sentenceCount = 0;

	}

	/**
	 * 
	 * Starts parsing the input and detect all words. The result extremely
	 * depends on the given parameter.
	 * 
	 * @param bar
	 *            ProgressBar (should be encapsulated later. this is lazy)
	 * @param text
	 *            Text input
	 * @param wordDelimiter
	 *            Indicates the end of a word. Regular expressions are possible
	 * @param sentenceDelimiter
	 *            Indicates the end of a sentence. Regular expressions are
	 *            possible.
	 * @param wordRegex
	 *            Boolean for marking the wordDelimiter as Regular Expression
	 * @param senRegex
	 *            Boolean for marking the sentenceDelimiter as Regular
	 *            Expression
	 * @param wordList
	 *            List of words that should be used to calculate the word
	 *            coverage.
	 * @return boolean for success of the text parsing.
	 */

	public boolean parseText(ProgressBar bar, String text,
			String wordDelimiter, String sentenceDelimiter, boolean wordRegex,
			boolean senRegex, List<String> wordList) {

		this.wordList = wordList;

		Pattern p;
		String[] meta;
		List<String> sentences = new ArrayList<String>();

		// compile pattern for sentence delimiter and split the input into
		// sentences depending on the delimiter
		try {
			if (!senRegex)
				p = Pattern.compile("\\" + sentenceDelimiter); //$NON-NLS-1$
			else
				p = Pattern.compile(sentenceDelimiter);

			meta = p.split(text);

			// number of sentences
			// decreases sentence count because last sentence is always an empty
			// one)
			sentenceCount = meta.length - 1;
			bar.setMaximum(sentenceCount);
			bar.setVisible(true);

			// add sentence to list
			for (String sen : meta)
				if (!sen.isEmpty())
					sentences.add(sen);

			// compile pattern for word delimiter and split every sentence into
			// words depending on the delimiter
			if (!wordRegex)
				p = Pattern.compile("\\" + wordDelimiter); //$NON-NLS-1$
			else
				p = Pattern.compile(wordDelimiter);

			// loop over all sentences and words to count words and add them
			// into a HashTable
			int barIndex;

			// sentence loop
			// decrease loop count because last sentence is always an empty one)
			if (useBaseform) {
				for (int i = 0; i < sentences.size() - 1; i++) {

					barIndex = i + 1;
					meta = p.split(sentences.get(i));
					bar.setSelection(barIndex);

					// word loop
					for (String s : meta) {

						s = s.trim();
						// empty check
						if (!s.isEmpty()) {

							/*
							 * At this point we extract one word from the input.
							 * 
							 * First: Delete all whitespace at beginning and end
							 * of the String (for security reasons if something
							 * happens that shouldn't happen:-)
							 * 
							 * Second: Reduce the word to its base form and
							 * convert it to lower case.
							 * 
							 * Third: Count the word and increase the counter
							 * for all words.
							 * 
							 * 
							 * Note: It is important that the lowerCase
							 * conversions happens AFTER the baseForm
							 * conversion. The part of speech detection respects
							 * the first letter of a word and for example at the
							 * German language a first upperCasse letter
							 * possible indicates a noun.
							 * 
							 * On the other hand the word counter doesn't
							 * respects upper- and lowerCase. Thats the reason
							 * for this order.
							 */

							s = baseFormHandler.getBaseForm(s);
							s = s.toLowerCase();
							countWords(s);
							wordCount++;

						}
					}
				}
			} else {
				// sentence loop without base form reduction
				for (int i = 0; i < sentences.size() - 1; i++) {

					barIndex = i + 1;
					meta = p.split(sentences.get(i));
					bar.setSelection(barIndex);

					// word loop
					for (String s : meta) {

						s = s.trim();
						// empty check
						if (!s.isEmpty()) {

							s = s.toLowerCase();
							countWords(s);
							wordCount++;

						}
					}

				}
			}
		} catch (PatternSyntaxException e) {

			IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench()
					.getActiveWorkbenchWindow();
			MessageDialog.openError(activeWorkbenchWindow.getShell(),
					Messages.TextProcessor_error,
					Messages.TextProcessor_invalid_delimiter);
			return false;
		}

		p = null;
		meta = null;
		sentences = null;
		return true;

	}

	/*
	 * Method counts words with the help of a HashMap.
	 * 
	 * If the map already contains a word, its count will be increased.
	 * Otherwise it will be put into the map and its count will be set to one.
	 */

	private void countWords(String word) {

		if (!countMap.containsKey(word))
			countMap.put(word, 1);
		else {
			Integer i = countMap.get(word);
			countMap.put(word, ++i);
		}

	}

	/**
	 * Getter for HashMap of word counts
	 * 
	 * @return CountMap
	 */

	public HashMap<String, Integer> getCounMap() {
		return countMap;
	}

	/**
	 * Getter for List that contains the word counts as String array.
	 * 
	 * Every item is an triple and has the following form:
	 * 
	 * [word][absolute frequency][relative frequency]
	 * 
	 * This form is specialized for the FrequencyTable at the ResultView. If you
	 * need the basic form call getCounMap().
	 * 
	 * @return CountList
	 */

	public List<String[]> getCountArray() {

		// transform a HashMap into an list

		Set<Entry<String, Integer>> entries = countMap.entrySet();
		Iterator<Entry<String, Integer>> it = entries.iterator();
		List<String[]> list = new ArrayList<String[]>();
		Float f;
		Entry<String, Integer> entry;
		while (it.hasNext()) {

			entry = it.next();
			f = ((float) entry.getValue()) / ((float) wordCount);
			list.add(new String[] { entry.getKey(),
					entry.getValue().toString(), f.toString() });

		}

		return list;
	}

	/**
	 * 
	 * Getter for statistical array.
	 * <p>
	 * This array is specialized for the StatisticTable at the ResultView. Array
	 * consists of tuple of key and value. The keys are:
	 * 
	 * 1) word count 2) sentence count 3) average sentence length 4) word
	 * coverage
	 * 
	 * @return
	 */

	public List<String[]> getStatisticArray() {

		List<String[]> list = new ArrayList<String[]>();
		list.add(new String[] { Messages.TextProcessor_wordCount,
				((Integer) wordCount).toString() });
		list.add(new String[] { Messages.TextProcessor_wordformCount,
				((Integer) getWordFormCount()).toString() });
		list.add(new String[] { Messages.TextProcessor_sentenceCount,
				((Integer) sentenceCount).toString() });
		list.add(new String[] { Messages.TextProcessor_avg_sentence_len,
				getAverageSentenceLenght() });
		list.add(new String[] { Messages.TextProcessor_word_coverage,
				getWordCoverage() });

		return list;
	}

	/**
	 * Getter for wordList
	 * <p>
	 * Creates a list that contains all words from the input. Every entry is a
	 * unique one, that mean no word is contained twice.
	 * 
	 * @return WordList
	 */

	public List<String> getwordList() {

		Set<String> keySet = countMap.keySet();
		Iterator<String> it = keySet.iterator();
		List<String> wordList = new ArrayList<String>();

		while (it.hasNext()) {
			wordList.add(it.next());
		}

		return wordList;
	}

	/**
	 * Getter for number of words form the input.
	 * 
	 * @return WordCount
	 */

	public int getWordCount() {
		return wordCount;
	}
	
	/**
	 * Getter for number of word forms form the input.
	 * 
	 * @return getWordFormCount
	 */

	public int getWordFormCount() {
		return countMap.size();
	}

	/**
	 * Getter for number of sentences form the input.
	 * 
	 * @return SentenceCount
	 */

	public int getSentenceCount() {
		return sentenceCount;
	}

	/**
	 * Getter for average sentence length from the input.
	 * 
	 * @return Average sentence length
	 */

	private String getAverageSentenceLenght() {
		return ((Float) (((float) wordCount) / ((float) sentenceCount)))
				.toString();
	}

	/*
	 * Calculates the word coverage for the given List of words. Method is
	 * called by getStatisticArray(). Thats why the returned value is a String.
	 */

	private String getWordCoverage() {

		int count = 0;
		Integer value;
		for (String s : wordList) {

			value = countMap.get(s);
			if (value != null)
				count += countMap.get(s);
		}

		return ((Float) ((float) count / (float) wordCount)).toString();
	}

}
